﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Runtime.CompilerServices;

using ApiDoc.Extensions;

namespace ApiDoc
{
    /// <summary>
    /// Api Documentation for a Type.
    /// </summary>
    public class TypeDocumentation : MemberDocumentationBase<Type>, IMemberDocumentation
    {
        internal TypeDocumentation(Type member, ApiDocumentationManager manager)
            : base(member, manager)
        {
        }

        public string Name
        {
            get
            {
                return Manager.CodeSyntaxGenerator.GenerateName(Member);
            }
        }

        public string CodeDefinition
        {
            get
            {
                return Manager.CodeSyntaxGenerator.GenerateCodeDefinition(Member);
            }
        }

        /// <summary>
        /// Gets the basic modifiers.
        /// </summary>
        public string Modifiers
        {
            get
            {
                if ((Member.Attributes & TypeAttributes.Public) == TypeAttributes.Public) {
                    return "public";
                } else if ((Member.Attributes & TypeAttributes.NestedAssembly) == TypeAttributes.NestedAssembly) {
                    return "internal";
                } else if ((Member.Attributes & TypeAttributes.NestedFamANDAssem) == TypeAttributes.NestedFamANDAssem) {
                    return "protected internal";
                } else if ((Member.Attributes & TypeAttributes.NestedFamORAssem) == TypeAttributes.NestedFamORAssem) {
                    return "protected internal";
                } else if ((Member.Attributes & TypeAttributes.NestedFamily) == TypeAttributes.NestedFamily) {
                    return "protected";
                } else if ((Member.Attributes & TypeAttributes.NestedPrivate) == TypeAttributes.NestedPrivate) {
                    return "private";
                } else if ((Member.Attributes & TypeAttributes.NestedFamily) == TypeAttributes.NestedFamily) {
                    return "protected";
                }

                return String.Empty;
            }
        }

        /// <summary>
        /// Namespace containing the type.
        /// </summary>
        public NamespaceDocumentation Namespace
        {
            get
            {
                return Manager.GetNamespaceDocumentation(Member.Namespace);
            }
        }

        /// <summary>
        /// Documentation of the assembly containing the type.
        /// </summary>
        public AssemblyDocumentation Assembly
        {
            get
            {
                return Manager.GetAssemblyDocumentation(Member.Assembly);
            }
        }

        /// <summary>
        /// Generic Type arguments.
        /// </summary>
        public IEnumerable<TypeDocumentation> GenericTypeArguments
        {
            get
            {
                if (m_genericTypeArguments == null) {
                    var ret = new List<TypeDocumentation>();

                    var arguments = Member.GetGenericArguments();
                    foreach (var argument in arguments) {
                        ret.Add(new TypeDocumentation(argument, Manager)); // NOTE we don't use the managers cache here as we wan't to ignore the cache.
                    }

                    m_genericTypeArguments = ret;
                }

                return m_genericTypeArguments;
            }
        }

        private IEnumerable<TypeDocumentation> m_genericTypeArguments;

        /// <summary>
        /// Gets the Constructors of this Type.
        /// </summary>
        public IEnumerable<ConstructorDocumentation> Constructors
        {
            get
            {
                if (m_constructors == null) {
                    m_constructors = Member.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
                        .Cast<ConstructorInfo>()
                        .Where(item => !item.IsPrivate) // TODO have an option to include
                        .Where(item => !item.IsAssembly) // TODO have an option to include
                        .Select(item => Manager.GetConstructorDocumentation(item))
                        .OrderBy(item => item.Name)
                        .ToList();
                }

                return m_constructors;
            }
        }

        private IEnumerable<ConstructorDocumentation> m_constructors;

        /// <summary>
        /// Gets the Methods of this Type.
        /// </summary>
        public IEnumerable<MethodDocumentation> Methods
        {
            get
            {
                if (m_methods == null) {
                    m_methods = Member.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
                        .Cast<MethodInfo>()
                        .Where(item => !item.IsSpecialName)
                        .Where(item => !item.IsPrivate) // TODO have an option to include
                        .Where(item => !item.IsAssembly) // TODO have an option to include
                        .Select(item => Manager.GetMethodDocumentation(item))
                        .OrderBy(item => item.Name)
                        .ToList();
                }

                return m_methods;
            }
        }

        private IEnumerable<MethodDocumentation> m_methods;

        /// <summary>
        /// Gets the Properties of this Type.
        /// </summary>
        public IEnumerable<PropertyDocumentation> Properties
        {
            get
            {
                if (m_properties == null) {
                    m_properties = Member.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
                        .Cast<PropertyInfo>()
                        .Where(item => item.CanRead) // TODO have support for CanWrite only properties
                        .Where(item => item.GetGetMethod() != null) // Will be null if get method is not private or protected.
                        .Where(item => !item.GetGetMethod().IsPrivate) // TODO have an option to include
                        .Where(item => !item.GetGetMethod().IsAssembly) // TODO have an option to include
                        .Select(item => Manager.GetPropertyDocumentation(item))
                        .OrderBy(item => item.Name)
                        .ToList();
                }

                return m_properties;
            }
        }

        private IEnumerable<PropertyDocumentation> m_properties;

        /// <summary>
        /// Gets the Fields of this Type.
        /// </summary>
        public IEnumerable<FieldDocumentation> Fields
        {
            get
            {
                if (m_fields == null) {
                    m_fields = Member.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
                        .Cast<FieldInfo>()
                        .Where(item => !item.IsPrivate) // TODO have an option to include
                        .Where(item => !item.IsAssembly) // TODO have an option to include
                        .Select(item => Manager.GetFieldDocumentation(item))
                        .OrderBy(item => item.Name)
                        .ToList();
                }

                return m_fields;
            }
        }

        private IEnumerable<FieldDocumentation> m_fields;

        /// <summary>
        /// Gets the Events of this Type.
        /// </summary>
        public IEnumerable<EventDocumentation> Events
        {
            get
            {
                if (m_events == null) {
                    m_events = Member.GetEvents(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
                        .Cast<EventInfo>()
                        .Where(item => !item.GetAddMethod().IsPrivate) // TODO have an option to include
                        .Where(item => !item.GetAddMethod().IsAssembly) // TODO have an option to include
                        .Select(item => Manager.GetEventDocumentation(item))
                        .OrderBy(item => item.Name)
                        .ToList();
                }

                return m_events;
            }
        }

        private IEnumerable<EventDocumentation> m_events;

        /// <summary>
        /// Gets the types contained within this namespace.
        /// </summary>
        public IEnumerable<TypeDocumentation> Types
        {
            get
            {
                if (m_types == null) {
                    // Find all nested types.
                    m_types = Member.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)
                        .Where(item => !item.IsSpecialName)
                        .Where(item => !item.IsDefined(typeof(CompilerGeneratedAttribute), false))
                        .Where(item => !item.IsNestedPrivate) // TODO have an option to include
                        .Where(item => !item.IsNestedAssembly) // TODO have an option to include
                        .Select(item => Manager.GetTypeDocumentation(item))
                        .OrderBy(item => item.Name)
                        .ToList();
                }

                return m_types;
            }
        }

        /// <summary>
        /// Gets the interfaces contained within this namespace.
        /// </summary>
        public IEnumerable<TypeDocumentation> Interfaces
        {
            get
            {
                return Types.Where(item => item.Member.IsInterface);
            }
        }

        /// <summary>
        /// Gets the classes contained within this namespace.
        /// </summary>
        public IEnumerable<TypeDocumentation> Classes
        {
            get
            {
                return Types.Where(item => item.Member.IsClass);
            }
        }

        /// <summary>
        /// Gets the enums contained within this namespace.
        /// </summary>
        public IEnumerable<TypeDocumentation> Enums
        {
            get
            {
                return Types.Where(item => item.Member.IsEnum);
            }
        }

        private IEnumerable<TypeDocumentation> m_types;


        /// <summary>
        /// Gets the parent type documentation this member is contained within.
        /// </summary>
        public TypeDocumentation ParentType
        {
            get
            {
                if (m_parentType == null) {
                    if (Member.ReflectedType == null) {
                        return null;
                    }

                    m_parentType = Manager.GetTypeDocumentation(Member.ReflectedType);
                }

                return m_parentType;
            }
        }

        private TypeDocumentation m_parentType;

        /// <summary>
        /// Gets the type documentation for the type this member was declared within.
        /// </summary>
        /// <remarks>
        /// This is the class the developer actually wrote the code of this method within.
        /// </remarks>
        public TypeDocumentation DeclaringType
        {
            get
            {
                if (m_declaringType == null) {
                    if (Member.DeclaringType == null) {
                        return null;
                    }

                    m_declaringType = Manager.GetTypeDocumentation(Member.DeclaringType);
                }

                return m_declaringType;
            }
        }

        private TypeDocumentation m_declaringType;

        /// <summary>
        /// Gets the type documentation for the base type of this member.
        /// </summary>
        public TypeDocumentation BaseType
        {
            get
            {
                if (m_baseType == null) {
                    if (Member.BaseType == null) {
                        return null;
                    }

                    m_baseType = Manager.GetTypeDocumentation(Member.BaseType);
                }

                return m_baseType;
            }
        }

        private TypeDocumentation m_baseType;

        /// <summary>
        /// Gets the type documentation for the base type of this member.
        /// </summary>
        /// <remarks>
        /// If this type is not a generic type or is already a generic type definition null is returned.
        /// </remarks>
        public TypeDocumentation GenericTypeDefinition
        {
            get
            {
                if (m_genericTypeDefinition == null) {
                    if (!Member.IsGenericType || Member.IsGenericTypeDefinition) {
                        return null;
                    }

                    m_genericTypeDefinition = Manager.GetTypeDocumentation(Member.GetGenericTypeDefinition());
                }

                return m_genericTypeDefinition;
            }
        }

        private TypeDocumentation m_genericTypeDefinition;

        // <summary>
        /// Gets the extension methods that directly affect this type.
        /// </summary>
        public IEnumerable<MethodDocumentation> ExtensionMethods
        {
            get
            {
                if (m_extensionMethods == null) {
                    // Find all types directly in this namespace.
                    m_extensionMethods = Manager.GetAssemblies()
                        .SelectMany(item => item.GetTypesSafe())
                        .Where(item => item.IsSealed && !item.IsGenericType)
                        .SelectMany(item => item.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
                        .Where(item => item.IsDefined(typeof(ExtensionAttribute), false))
                        .Where(item => item.GetParameters()[0].ParameterType == Member)
                        .Select(item => Manager.GetMethodDocumentation(item))
                        .OrderBy(item => item.Name)
                        .ToList();
                }

                return m_extensionMethods;
            }
        }

        private IEnumerable<MethodDocumentation> m_extensionMethods;


        /// <summary>
        /// Gets the type documentation for the base type of this member.
        /// </summary>
        public TypeDocumentation ElementType
        {
            get
            {
                if (m_elementType == null) {
                    if (!Member.HasElementType) {
                        return null;
                    }

                    m_elementType = Manager.GetTypeDocumentation(Member.GetElementType());
                }

                return m_elementType;
            }
        }

        private TypeDocumentation m_elementType;
    }
}
